use crate::dto;
use crate::service::Error;
use crate::service::auth_token::AuthTokenService;
use crate::service::user::UserService;
use redis::AsyncCommands;
use serde::{Deserialize, Serialize};
use serde_with::base64::Base64;
use std::sync::Arc;
use wanmesh_common::dto::ResponseCode;
use wanmesh_common::error::{ResponseError, ResponseErrorAny};

const LOGIN_REQUEST_EXPIRES_IN: u64 = 60;

#[serde_with::serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
struct LoginRequestInfo {
    user_id: String,
    #[serde_as(as = "Base64")]
    private_key: Vec<u8>,
}

#[async_trait::async_trait]
pub trait AuthService: Send + Sync {
    async fn login_apply(
        &self,
        request: &dto::auth::LoginApplyRequest,
    ) -> Result<dto::auth::LoginResponseData, Error>;

    async fn login_submit(
        &self,
        request: &dto::auth::LoginSubmitRequest,
    ) -> Result<dto::auth::Token, Error>;

    async fn refresh_token(
        &self,
        request: &dto::auth::RefreshTokenRequest,
    ) -> Result<dto::auth::Token, Error>;

    async fn get_auth_info(&self, access_token: &str) -> Result<dto::auth::AuthInfo, Error>;
}

pub struct DefaultAuthService {
    user_service: Arc<dyn UserService>,
    auth_token_service: Arc<dyn AuthTokenService>,
    redis_conn: redis::aio::MultiplexedConnection,
}

impl DefaultAuthService {
    pub fn new(
        user_service: Arc<dyn UserService>,
        auth_token_service: Arc<dyn AuthTokenService>,
        redis_conn: redis::aio::MultiplexedConnection,
    ) -> Self {
        Self {
            user_service,
            auth_token_service,
            redis_conn,
        }
    }

    async fn get_user(&self, username: &str) -> Result<dto::core::user::User, Error> {
        match self.user_service.try_get(username).await? {
            None => Err(Error::ResponseError(err_user_not_found(username))),
            Some(v) => Ok(v),
        }
    }

    async fn store_login_request(
        &self,
        apply_id: &str,
        info: &LoginRequestInfo,
    ) -> Result<(), Error> {
        let key = key_login_apply(apply_id);
        let info = serde_json::to_string(info).map_err(anyhow::Error::new)?;

        let mut redis_conn = self.redis_conn.clone();

        let _: () = redis_conn
            .set_ex(key, info, LOGIN_REQUEST_EXPIRES_IN)
            .await
            .map_err(anyhow::Error::new)?;

        Ok(())
    }

    async fn load_login_request(&self, apply_id: &str) -> Result<LoginRequestInfo, Error> {
        let mut redis_conn = self.redis_conn.clone();
        let info: Option<String> = redis_conn
            .get(key_login_apply(apply_id))
            .await
            .map_err(anyhow::Error::new)?;

        let info: LoginRequestInfo = match info {
            Some(info) => serde_json::from_str(info.as_str()).map_err(anyhow::Error::new)?,
            None => {
                return Err(Error::ResponseError(ResponseError {
                    code: ResponseCode::UNAUTHORIZED,
                    message: "无效的apply_id".to_string(),
                    backtrace: Default::default(),
                }));
            }
        };
        Ok(info)
    }

    async fn delete_login_request(&self, apply_id: &str) -> Result<(), Error> {
        let key = key_login_apply(apply_id);

        let mut redis_conn = self.redis_conn.clone();
        let _: () = redis_conn.del(key).await.map_err(anyhow::Error::new)?;

        Ok(())
    }
}

#[async_trait::async_trait]
impl AuthService for DefaultAuthService {
    async fn login_apply(
        &self,
        request: &dto::auth::LoginApplyRequest,
    ) -> Result<dto::auth::LoginResponseData, Error> {
        let user = self.get_user(request.username.as_str()).await?;

        let apply_id = wanmesh_common::util::new_id()?;

        let (private_key, public_key) = wanmesh_common::auth::new_key_pair()?;

        let private_key_bytes = private_key.to_bytes()?;
        let public_key_bytes = public_key.to_bytes()?;

        self.store_login_request(
            apply_id.as_str(),
            &LoginRequestInfo {
                user_id: user.user_id,
                private_key: private_key_bytes,
            },
        )
        .await?;

        Ok(dto::auth::LoginResponseData {
            apply_id,
            public_key: public_key_bytes,
        })
    }

    async fn login_submit(
        &self,
        request: &dto::auth::LoginSubmitRequest,
    ) -> Result<dto::auth::Token, Error> {
        let user = self.get_user(request.username.as_str()).await?;

        let info = self.load_login_request(request.apply_id.as_str()).await?;
        if info.user_id != user.user_id {
            return Err(Error::ResponseError(ResponseError {
                code: ResponseCode::UNAUTHORIZED,
                message: "用户不匹配".to_string(),
                backtrace: Default::default(),
            }));
        }

        self.delete_login_request(request.apply_id.as_str()).await?;

        let private_key = wanmesh_common::auth::PrivateKey::try_from(info.private_key.as_slice())?;
        let password = private_key
            .decrypt(request.password_crypto.as_slice())
            .map_err(|e| {
                Error::ResponseErrorAny(ResponseErrorAny {
                    code: ResponseCode::UNAUTHORIZED,
                    message: format!(
                        "密码解密失败，username={}，apply_id={}",
                        request.username, request.apply_id
                    ),
                    source: e,
                })
            })?;

        let password = String::from_utf8_lossy(password.as_slice());

        self.user_service
            .verify_password(request.username.as_str(), &password)
            .await?;

        self.auth_token_service
            .create_token(&dto::auth::AuthInfo {
                user_id: user.user_id,
            })
            .await
    }

    async fn refresh_token(
        &self,
        request: &dto::auth::RefreshTokenRequest,
    ) -> Result<dto::auth::Token, Error> {
        self.auth_token_service
            .refresh_token(request.refresh_token.as_str())
            .await
    }

    async fn get_auth_info(&self, access_token: &str) -> Result<dto::auth::AuthInfo, Error> {
        self.auth_token_service.get_auth_info(access_token).await
    }
}

fn key_login_apply(login_apply: &str) -> String {
    format!("wanmesh:auth:login_apply:{}", login_apply)
}

fn err_user_not_found(username: &str) -> ResponseError {
    ResponseError {
        code: ResponseCode::UNAUTHORIZED,
        message: format!("用户不存在, username={}", username),
        backtrace: Default::default(),
    }
}
